ODPP coat of arms
Office of the Director of Public Prosecutions · Inspections & Quality Assurance

Complaints Management System — Record Blueprint

Entity model, lifecycle, numbering, permissions and Django app layout. Written against the Jinja Road → ODPP → Nakawa Court paper chain.
Decision first — how public should this be?
Recommendation: a public front door and a public keyhole — never a public register.
Anyone may lodge a complaint without an account. In return they get a reference number and a 6-digit PIN, and that pair unlocks exactly one thing: the status of their own complaint, in plain language, plus the dates of the last and next expected action. No case detail, no officer names, no document text, no searchable index. Everything else — narratives, attachments, inquiry notes, the linked case file — lives behind staff authentication.
Why not accounts
A complainant may have no email, no smartphone and one visit to the office. An account is a barrier that suppresses complaints from exactly the people the Inspectorate exists to hear.
Why not a public register
A Type A complaint names a serving officer. A browsable register is a witness-intimidation tool and a defamation exposure. Reference + PIN keeps disclosure one-to-one.
What is public
Aggregates only: complaints received, median resolution days, share resolved within SLA, per region. Published quarterly, no case-level rows. That is your accountability story.
Reference numbering
Human-readable, quotable over a toll-free line, and never guessable. The public reference is deliberately not sequential-looking to the caller: the sequence is per-year-per-region, and the PIN is the actual secret.
Public complaint reference
CMP/NAK/2026/00412
Prefix · region code · year · zero-padded sequence within region and year. Printed on the counterfoil receipt for walk-ins and letters; shown once on screen and emailed or SMS'd where contact exists.
Paired PIN: 4 7 2 9 1 6 — stored hashed, never redisplayed, rate-limited to 5 attempts per hour per reference.
Internal Affairs file (Type A only)
IAF/2026/0037
Allocated only after classification, in a separate sequence with no region code — the region would identify the officer. The complainant never sees this number; they continue to quote their CMP reference.
The mapping CMP → IAF is itself a restricted record, readable only by Internal Affairs and the Head of Complaints.
Police diarySD 14/26/08/2026
Station caseCRB 412/2026
ODPP fileDPP/JNJ/412/2026
CourtNAK-00-CR-CO-0512-2026
Complaint lifecycle — the state machine
One forward path with three exits. Every transition writes a ComplaintEvent row — status is derived from the latest event, never edited in place. The SLA column is the clock the executive dashboard reads.
#
State
What happens
SLA
Actor
{{ s.n }}
{{ s.code }}
{{ s.what }}
{{ s.sla }}
{{ s.actor }}
Exit · Withdrawn
Complainant withdraws. Record retained, narrative retained, no determination made. Reason captured as free text.
Exit · Referred out
Not an ODPP matter — Police Professional Standards Unit, Judiciary, IGG. Referral letter reference stored; complainant told where it went.
Escalation · any state
SLA breach or a second complaint on the same case auto-escalates to the Head of Complaints and appears red on the executive dashboard.
Entity model — five app boundaries
Field names are the ones to migrate. Red rows are restricted — they must never appear in a queryset reachable by a non-Internal-Affairs user.
{{ app.name }}
{{ app.label }}
{{ app.note }}
{{ m.name }}
{{ m.purpose }}
{{ m.flag }}
{{ m.fields }}
Who sees what
Enforce at queryset level in a manager, not in templates. Type A narratives are the single hardest boundary in the system: the accused officer is a legitimate authenticated staff user with a real reason to be in the app.
full access conditional — own record, own region, or with sign-off no access
Capability
Citizen
Clerk
Head of Complaints
Internal Affairs
RSA / Region
DPP
{{ p.cap }}
{{ p.a }}
{{ p.b }}
{{ p.c }}
{{ p.d }}
{{ p.e }}
{{ p.f }}
Type A isolation — four mechanisms, not one
  1. Separate database (Django multi-db router on the conduct app) so a mistaken join cannot leak.
  2. Default manager returns none() unless the request user is in the Internal Affairs group.
  3. Every successful read writes an AccessLog row: user, object, timestamp, IP. Reads are evidence.
  4. Subject officer's own user id is checked on retrieval — even Internal Affairs staff cannot open a file naming themselves.
Latency, computed not stored
Two clocks per Type B complaint. Complaint age = today − received_at, drives the Inspectorate SLA. Case dwell = today − case.ingested_at_registry, drives the substantive question the complainant is asking.
0–13 days · normal 14–29 · flagged 30+ · escalated to DPP
Nightly job materialises the deltas into a read-only summary table for the dashboard; source of truth stays the event log.
Two intake channels, one record
Web form — complainant self-service
Four steps, no account: who you are (with an explicit anonymous option that disables tracking), what happened, which case if any (CRB or ODPP reference, optional), evidence (optional upload). Submission returns reference + PIN on a printable confirmation.
Classification into Type A or B is never asked of the complainant — they describe, staff classify. Asking teaches people to mislabel to get faster routing.
Letter / physical — clerk-captured
The clerk keys the same form on behalf of the writer, sets source = LETTER, records date_on_letter separately from received_at (postal lag is a real latency source), scans the letter as the first attachment, and prints a two-part counterfoil: reference + PIN to the complainant, stub to the file.
Where the writer left no phone or address, the system marks the complaint uncontactable — determination is still recorded, and the outcome is held for collection.
Build order
Police-side records are reference numbers only for now, per your call — CaseReference is a thin model from day one so the full mirror can be added later without touching the complaints tables.
Milestone 1
Directory + intake
Seed 18 regions and their RSA/RSP stations. Web form, clerk capture, reference + PIN issue, counterfoil print. No triage yet.
Milestone 2
Triage + event log
Classification into A / B, assignment, ComplaintEvent transitions, the desk queue, and the public status keyhole.
Milestone 3
Isolation + audit
Conduct app on its own database, restricted managers, AccessLog on every read, self-recusal check.
Milestone 4
Latency + executive view
Nightly delta job, thresholds at 14 and 30 days, regional league table, quarterly public aggregate export.
Companion to the ODPP Brand & Process Reference. Palette and type from the ODPP crest and the bound Sprint type stack. Blueprint v1 · 26 August 2026
Built by Nova Generation
Powered by Sprint UG — Live Unlimited